package digitaldreamers.libraries.networkmanagement.socket;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;

import android.os.AsyncTask;
import android.os.Handler;
import android.os.SystemClock;

public class ConnectionHandler {
	private static final String ERROR_MESSAGE__INVALID_PACKET_LENGTH = "Packet length must be greater than 0 and less than 1025.";
	private static final String ERROR_MESSAGE__DATA_READING = "Could not read packet data.";
	
	private static final int MAX_PACKET_LENGTH_BYTES = 1024;
	private static final int PACKET_LENGTH_BYTES = 4;
	private static final int SLEEP_TIME = 100;
	
	private static ConnectionHandler instance;
	private static Object lockObj = new Object();
	
	public volatile boolean mIsRunning;
	private boolean mIsConnected;
	private String mIP;
	private int mPort;
	private Socket mSocket;
	private DataOutputStream mDataOutputStream;
	private InputStream mInputStream;
	Handler mHandler;
	private ArrayList<IOnDataReceiverListener> mListeners;
	
	public static ConnectionHandler getInstance() {
		if(instance == null) {
			synchronized(lockObj) {
				if(instance == null) {
					instance = new ConnectionHandler();
				}
			}
		}
		return instance;
	}
	
	private ConnectionHandler() {
		this.mIsRunning = false;
		this.mIsConnected = false;
		this.mHandler = new Handler();
		this.mListeners = new ArrayList<ConnectionHandler.IOnDataReceiverListener>();
	}

	public void connectToServer(String ip, int port) throws UnknownHostException, IOException {
		this.mIP = ip;
		this.mPort = port;
		ConnectionAsync connectionAsync = new ConnectionAsync();
		connectionAsync.execute(this);
	}
	
	public void send(byte[] data) throws IOException {
		mDataOutputStream.write(convertIntToByteArray(data.length));
		mDataOutputStream.flush();
		mDataOutputStream.write(data);
		mDataOutputStream.flush();
	}

	public void addEventListener(IOnDataReceiverListener listener) {
		mListeners.add(listener);
	}

	public void removeEventListener(IOnDataReceiverListener listener) {
		mListeners.remove(listener);
	}
	
	private final byte[] convertIntToByteArray(int value) {
		return new byte[] {
			(byte) (value >>> 24),
			(byte) (value >>> 16),
			(byte) (value >>> 8),
			(byte) value
		};
	}

	public static interface IOnDataReceiverListener {
		public abstract void onDataReceiver(byte[] data) throws IOException;
	}
	
	private class ConnectionAsync extends AsyncTask<ConnectionHandler, Void, Void> {
		private ConnectionHandler mConnectionHandler;

		@Override
		protected Void doInBackground(ConnectionHandler... params) {
			this.mConnectionHandler = params[0];
			if (mConnectionHandler.mIsConnected) {
				return null;
			}

			try {
				this.connectToServer();
			} catch (UnknownHostException e) {
				e.printStackTrace();
				return null;
			} catch (IOException e) {
				e.printStackTrace();
				return null;
			}
			
			this.receiveData();
			return null;
		}

		@Override
		protected void onPostExecute(Void value) {
			try {
				mConnectionHandler.mSocket.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		private void connectToServer() throws UnknownHostException, IOException {
			mConnectionHandler.mSocket = new Socket(mConnectionHandler.mIP, mConnectionHandler.mPort);
			mConnectionHandler.mIsRunning = true;
			mConnectionHandler.mIsConnected = true;
			mConnectionHandler.mInputStream = mConnectionHandler.mSocket.getInputStream();
			mConnectionHandler.mDataOutputStream = new DataOutputStream(new BufferedOutputStream(mConnectionHandler.mSocket.getOutputStream()));
		}
		
		private void receiveData() {
			while (mConnectionHandler.mIsRunning) {
				try {
					if (mConnectionHandler.mInputStream.available() > 0) {
						byte[] data = this.receiveFixedData(mConnectionHandler.mInputStream, PACKET_LENGTH_BYTES);
						if (data == null) {
							throw new Exception(ERROR_MESSAGE__DATA_READING);
						}

						// Get message length
						ByteArrayInputStream BAIS = new ByteArrayInputStream(data);
						DataInputStream DIS = new DataInputStream(BAIS);
						int packetLength = DIS.readInt();
						
						if (packetLength > 0) {
							data = this.receiveFixedData(mConnectionHandler.mInputStream, packetLength);
							if (data == null) {
								throw new Exception(ERROR_MESSAGE__DATA_READING);
							}
						}
						
						//this.checkNotification(data);
						this.fireDataReceiveEvent(data);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
				SystemClock.sleep(SLEEP_TIME);
			}
		}
		
		private byte[] receiveFixedData(InputStream inputStream, int count) throws Exception {
			// Check the data
			if (count <= 0 || count > MAX_PACKET_LENGTH_BYTES) {
				throw new IllegalArgumentException(ERROR_MESSAGE__INVALID_PACKET_LENGTH);
			}

			// Set the buffer with the input length
			byte[] buffer = new byte[count];
			int receivedCount;
			int position = 0;
			
			// Read until all data is received.
			do {
				// Read the stream to buffer
				receivedCount = inputStream.read(buffer, position, count - position);
				if (receivedCount == 0) {
					throw new Exception(ERROR_MESSAGE__DATA_READING);
				}
				position += receivedCount;
			} while (position < count);
			return buffer;
		}
		
		private void fireDataReceiveEvent(byte[] data) throws IOException {
			IOnDataReceiverListener[] listeners = new IOnDataReceiverListener[this.mConnectionHandler.mListeners.size()];
			this.mConnectionHandler.mListeners.toArray(listeners);
			for (IOnDataReceiverListener listener : listeners) {
				listener.onDataReceiver(data);
			}
		}
	}

	/*
	 * // check notification private void checkNotification(byte[] data) { try {
	 * ByteArrayInputStream BAIS = new ByteArrayInputStream(data);
	 * DataInputStream DIS = new DataInputStream(BAIS); int packetID =
	 * DIS.readInt(); if (packetID == 99) { // Read the packet int iStatusID =
	 * DIS.readInt(); String strMessage = DIS.readUTF(); String strSenderName =
	 * DIS.readUTF();
	 * 
	 * // } } catch (IOException e) { // TODO Auto-generated catch block
	 * e.printStackTrace(); }
	 * 
	 * }
	 */
}
